home *** CD-ROM | disk | FTP | other *** search
/ Chip 2000 November / Chip Kasım 2000.iso / prog / share / 11 / setup.exe / %MAINDIR% / DEMOS / CIFTP / FTPCLASS / CLSFTP.CLS next >
Encoding:
Text File  |  2000-09-07  |  58.7 KB  |  1,517 lines

  1. VERSION 1.0 CLASS
  2. BEGIN
  3.   MultiUse = -1  'True
  4. END
  5. Attribute VB_Name = "clsFTP"
  6. Attribute VB_Creatable = False
  7. Attribute VB_Exposed = False
  8. Option Explicit
  9.  
  10. '~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  11. '
  12. ' SUBJECT:      clsFTP
  13. ' AUTHOR:       David M Swan
  14. '                      Progress Software Corporation
  15. '                      Crescent Division
  16. '
  17. ' CREATED:     December 8, 1996
  18. '
  19. ' REVISION
  20. ' HISTORY:
  21. '
  22. ' DESCRIPTION: Encapsulates many of the CIFTP control's functions in a class module
  23. '                         See FtpClass.wri for detailed description of the class.
  24. '
  25. ' PUBLIC
  26. ' INTERFACE: FUNCTIONS - bLogin, bLogout, bGetDirectory, bGetFile, bPutFile, bAbort,
  27. '                                            sGetLastError, lTotalDataBytesIn, lTotalDataBytesOut
  28. '
  29. ' PUBLIC PROPERTIES - iTimeoutValue, bDumpAccessPackets, bDumpDataPackets
  30. '
  31. '~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  32.  
  33. ' ========================
  34. ' Define the public class members:
  35. ' ========================
  36.  
  37. Public iTimeoutValue As Integer ' max # of seconds to wait for a server response before bailing out. Set to 0 for infinite wait
  38. ' Debug control flags...
  39. Public bDumpAccessPackets  As Boolean       ' Controls whether Access Control Packets are dumped to debug window
  40. Public bDumpDataPackets  As Boolean           ' Controls whether Data Channel Packets are dumped to debug window
  41.  
  42. ' ========================
  43. ' Define the private class members:
  44. ' ========================
  45. Private objFTP As CIFTP                 ' The FTP control being encapsulated - This is set in bInit
  46. Private bInitialized As Boolean         ' Set to True once bInit has been called successfully
  47. Private bUserLoggingOut As Boolean      ' Flag indicating whether the user is logging out (set to True in user func bLogout)
  48.                                                             ' This is used to determine if the Access Channel is closing due to a logout request (via bLogout)
  49.                                                             ' or was initiated by the server.
  50. Private bAccessControlChannelOpen As Boolean      ' Set to True when EventState = CIFTP_ACCONN
  51.                                                                              ' Set to False when EventState = CIFTP_ACCLOSED
  52. Private bDataControlChannelOpen As Boolean          ' Set to True when EventState = CIFTP_DCCONN
  53.                                                                              ' Set to False when EventState = CIFTP_DCCLOSED
  54. Private dtLastUserRequest As Date ' Time at which the user last called a public function
  55. Private dtLastServerResponse As Date ' Used to track the time the last server data was received
  56.                                                          ' (Set in: DataControlPacketRecieved/Sent, EventStateChanged and ServerResponse Events)
  57.  
  58. Private sCurrUserFuncName As String ' Name of the currently executing client function
  59. Private bFileClosed As Boolean ' Set to True in EventStateChanged, to False in bExecCmd.  Used for LIST and RETR cmd processing
  60.  
  61. Private m_lTotalDataBytesIn As Long ' counter for total bytes that come in during a data transfer (to the client -- RETR)
  62. Private m_lTotalDataBytesOut As Long ' counter for total bytes that are sent out during a data transfer (to the server -- STOR)
  63. ' Note use the corresponding public Property Get functions to access these from client code
  64.  
  65. ' Consts identifying each of the public functions (used by DumpFTPSettings)
  66. Private Const USER_FUNC_NONE As Byte = 0
  67. Private Const USER_FUNC_LOGIN As Byte = 1
  68. Private Const USER_FUNC_LOGOUT As Byte = 2
  69. Private Const USER_FUNC_GET_DIR As Byte = 3
  70. Private Const USER_FUNC_GET_FILE As Byte = 4
  71. Private Const USER_FUNC_PUT_FILE As Byte = 5
  72.  
  73. Private Const DEFAULT_TIMEOUT As Integer = 60 ' Default timeout in seconds
  74.  
  75. Private Const ERROR_TYPE_NONE As Byte = 0           ' No Error
  76. Private Const ERROR_TYPE_WSA As Byte = 1            ' WSAError event fired
  77. Private Const ERROR_TYPE_INTERNET As Byte = 2    ' InternetError event fired
  78. Private Const ERROR_TYPE_OTHER As Byte = 3       ' Catch all for all other types of errors
  79.                                                           ' These are either usually caused by bad parameters
  80.                                                           ' or an attempt by the user to call a function without
  81.                                                           ' having the system in the proper state (e.g. calling a
  82.                                                           ' function w/o first logging in)
  83.                                                           
  84. ' Values when LastError.iType = ERROR_TYPE_OTHER
  85. ' ========================================
  86. Private Const ERR_ROUTINE_ERROR As Byte = 1
  87. Private Const ERR_BAD_OR_MISSING_PARAM As Byte = 2
  88. Private Const ERR_USER_ERROR As Byte = 3 ' e.g. user called Init but the class is already initialized
  89. Private Const ERR_CLASS_NOT_INITIALIZED As Byte = 4 ' user is calling a function w/o first initializing the class
  90. Private Const ERR_UNEXPECTED_STATE As Byte = 5  ' e.g. user calls bLogout, but there access channel is closed
  91. Private Const ERR_SERVER_REQUEST_FAILED As Byte = 6 ' catch all for errors calling bExecCmd
  92. Private Const ERR_ACCESS_CONN_LOST As Byte = 7 ' set in the case that the access channel drops unexpectedly
  93. Private Const ERR_TIMED_OUT As Byte = 8 ' timeout period expired waiting on an operation to complete
  94. Private Const ERR_INVALID_FUNC_CALL As Byte = 9 ' user attempted to call a function before another function had completed
  95. Private Const ERR_CMD_ABORTED As Byte = 10 ' the user called bAbort while a user function was executing
  96. Private Const ERR_INVALID_ABORT As Byte = 11 ' the user called bAbort, but no function was executing at the time
  97. Private Const ERR_NOT_CONNECTED As Byte = 12 ' attempted to call a function but there is no server connection
  98.  
  99. Private Type udtERROR
  100.   iType As Integer      ' NONE, OTHER, WSA, or INTERNET
  101.   lCode As Long
  102. End Type
  103. Private LastError As udtERROR       ' Used to track error settings.  Use SetError to load the structure
  104.  
  105. Private iInternaleError As Integer  ' Used to save Err.Number when an internal error occurs
  106.  
  107. ' As server responses come in (via the CIFTP.ServerResponse event firing), we queue the information so
  108. ' that we can determine the result of server requests.  The sequence is as follows: (1) We establish a
  109. ' data channel if necessary, (2) We issue a server request via bExecCmd, (3) As response(s) come in from
  110. ' the server, we queue them via AddToSRQueue, (4) We wait for the server's response via bWaitOnServerResponse.
  111. ' This routine looks in the response queue to determine the result of the request.  All of these functions are
  112. ' private (and therefore hidden from the user).
  113.  
  114. Private Type SR_NODE
  115.   iCode As Integer
  116.   sMsg As String
  117. End Type
  118.  
  119. Private Const QUEUE_SIZE As Byte = 10
  120. Private SRQueue(1 To QUEUE_SIZE) As SR_NODE
  121. Private iQPos As Integer   ' current Queue position
  122.  
  123. ' Consts representing the commands supported by bExecCmd function...
  124. '====================================================
  125. Private Const CMD_USER          As Byte = 1
  126. Private Const CMD_PASS          As Byte = 2
  127. Private Const CMD_CWD           As Byte = 3
  128. Private Const CMD_CDUP          As Byte = 4
  129. Private Const CMD_QUIT            As Byte = 5
  130. Private Const CMD_PASV          As Byte = 6
  131. Private Const CMD_TYPE          As Byte = 7
  132. Private Const CMD_STOR          As Byte = 8
  133. Private Const CMD_RETR          As Byte = 9
  134. Private Const CMD_LIST            As Byte = 10
  135. Private Const CMD_APPE          As Byte = 11
  136. Private Const CMD_PWD           As Byte = 12
  137. Private Const CMD_SYST          As Byte = 13
  138. Private Const NUM_SUPPORTED_CMDS As Byte = 13 ' MAKE SURE TO INCREASE THIS IF YOU ADD ADDITIONAL COMMANDS!!!
  139.  
  140. Private Const SUCCESS As Byte = 1
  141. Private Const FAILURE As Byte = 2
  142.  
  143. Private Const MAX_CODES As Byte = 15 ' Max # of codes indicating success or failure of a given server request
  144.  
  145. ' This array represents the FTP state machine.  The first dimension represents the ftp command being executed.
  146. ' The second dimension represents success or failure and the third dimension is the set of possible result codes
  147. ' for that particular command's successfull/failing values.  The machine is initialized at startup by InitFTPStateMachine
  148. Private iFTPStateMachine(1 To NUM_SUPPORTED_CMDS, 1 To 2, 1 To MAX_CODES) As Integer
  149.  
  150.   
  151.  
  152. ' Ideas for future enhancements...
  153. ' ============================
  154.  
  155. ' 1) Add an error collection
  156. ' 2) Add an actions (audit trail) collection to track each requested action and it's result.
  157. ' 2a) Audit trail log file
  158. ' 3) Optionally dump "DP" output to a window for run-time debugging/tracking
  159. ' 4) Add auto reconnect if the ftp server drops a connection after a period of inactivity.
  160. ' 5) Wrap the control's high level methods
  161. ' 6) Implement a function that wraps the SendFTPCommand function
  162.  
  163. '====================================================
  164. '   +++ Public Routine Implementation Starts Here +++
  165. '====================================================
  166.  
  167. Public Function bInit(FTP As CIFTP) As Boolean
  168.  
  169. ' Usage: Users MUST call this function first passing the CIFTP control they wish to wrap as the sole argument
  170.   
  171.   On Error GoTo bInit_ErrHdlr
  172.   
  173.   ClearErrorFlags
  174.   dtLastUserRequest = Now
  175.   
  176.   If bInitialized Then
  177.     bInit = False
  178.     SetError ERR_USER_ERROR
  179.     DP "ERROR - bInit already called!"
  180.   Else
  181.     Set objFTP = FTP
  182.     InitFTPStateMachine
  183.     bInitialized = True
  184.     bInit = True
  185.   End If
  186.   
  187.   Exit Function
  188.   
  189. bInit_ErrHdlr:
  190.   HandleInternalError
  191.   
  192. End Function ' bInit
  193.  
  194. Public Function bLogin(Optional vntHostName, Optional vntLoginName, Optional vntPassword, Optional vntWorkingDirectory) As Boolean
  195.     
  196. ' Usage: After calling bInit, you can call this function to establish a connection with an ftp server.  Be sure to either set the
  197. '            HostName, LoginName and Password properties in the CIFTP control or pass these as arguments to the function.
  198. '            This wraps the ConnectToAccessControlChannel, USER, PASS, CWD and PWD methods.
  199.  
  200. '            Note that you can pass either the host name or an ip address in the vntHostName argument.
  201.  
  202.   Dim bSuccess As Boolean
  203.   Const SR_USER_OK_NEED_PASS As Integer = 331
  204.   
  205.   On Error GoTo bLogin_ErrHdlr
  206.   
  207.   bLogin = False ' Assume worst case
  208.   
  209.   If Not bInitUserCall("bLogin") Then Exit Function 'GoTo bLogin_GetOut
  210.   
  211.   ' Handle optional params...
  212.   If Not IsMissing(vntHostName) Then
  213.     ' Determine if it's an IP address...
  214.     If bIsIPAddress(CStr(vntHostName)) Then
  215.       objFTP.HostAddress = CStr(vntHostName)
  216.     Else
  217.       objFTP.HostName = CStr(vntHostName)
  218.     End If
  219.   End If
  220.   If Not IsMissing(vntLoginName) Then objFTP.LoginName = CStr(vntLoginName)
  221.   If Not IsMissing(vntPassword) Then objFTP.Password = CStr(vntPassword)
  222.   If Not IsMissing(vntWorkingDirectory) Then objFTP.WorkingDirectory = CStr(vntWorkingDirectory)
  223.   
  224.   DumpFTPSettings (USER_FUNC_LOGIN)  ' Dump relevant FTP login settings
  225.   
  226.   ' Make sure that either a host name or address has been set...
  227.   If objFTP.HostName = "" And objFTP.HostAddress = "" Then
  228.     SetError ERR_BAD_OR_MISSING_PARAM
  229.     DP "ERROR: HostName or HostAddress must be set"
  230.     GoTo bLogin_GetOut
  231.   End If
  232.   
  233.   ' Establish an access control channel with the ftp server...
  234.   If Not bConnectToAccessControlChannel() Then GoTo bLogin_GetOut
  235.       
  236.   ' Login and set the working directory...
  237.   If Not bExecCmd(CMD_USER) Then GoTo bLogin_GetOut
  238.   ' Only send the password if the server requires it...
  239.   If bCheckSRQueue(SR_USER_OK_NEED_PASS) Then
  240.     DP "Server needs PASS, sending..."
  241.     If Not bExecCmd(CMD_PASS) Then GoTo bLogin_GetOut
  242.   End If
  243.   
  244.   ' (Possibly) change to the dir specified in the WorkingDirectory property
  245.   If objFTP.WorkingDirectory <> "" Then
  246.     If Not bExecCmd(CMD_CWD) Then GoTo bLogin_GetOut
  247.   End If
  248.   
  249.   ' Update the WorkingDirectory property...
  250.   If Not bExecCmd(CMD_PWD) Then GoTo bLogin_GetOut
  251.                 
  252.   bLogin = True ' if we got here, then we've successfully connected
  253.     
  254. bLogin_GetOut:
  255.   
  256.   EndUserCall
  257.   Exit Function
  258.   
  259. bLogin_ErrHdlr:
  260.   HandleInternalError
  261.   EndUserCall
  262.  
  263. End Function ' bLogin
  264.  
  265. Public Function bGetFile(Optional vntLocalFileName, Optional vntRemoteFileName, Optional vntBinaryFile) As Boolean
  266.  
  267. ' Usage: Get a file using the low level ftp methods.  Be sure to set the CIFTP control's LocalFileName, RemoteFilename properties
  268. '             before calling this function or pass these as arguments to the funciton.  This wraps the PASV, ConnectToDataChannel
  269. '             and RETR methods.  If vntBinaryFile is True then the RepresentationType is set to binary (I) otherwise it is set
  270. '             to ASCII.  If it is not supplied, binary is assumed
  271.  
  272.   Dim bSuccess As Boolean, bBinaryFile As Boolean
  273.  
  274.   On Error GoTo bGetFile_ErrHdlr
  275.   
  276.   bGetFile = False ' Assume worst case.
  277.   
  278.   If Not bInitUserCall("bGetFile") Then Exit Function 'GoTo bGetFile_GetOut
  279.     
  280.   ' Handle optional parameters...
  281.   If Not IsMissing(vntLocalFileName) Then objFTP.LocalFileName = CStr(vntLocalFileName)
  282.   If Not IsMissing(vntRemoteFileName) Then objFTP.RemoteFileName = CStr(vntRemoteFileName)
  283.   If Not IsMissing(vntBinaryFile) Then
  284.     bBinaryFile = CBool(vntBinaryFile)
  285.   Else
  286.     bBinaryFile = True ' Treat as a binary file if not specified.
  287.   End If
  288.                                         
  289.   DumpFTPSettings (USER_FUNC_GET_FILE)  ' Dump relevant FTP settings for getting a file
  290.  
  291.   ' Make sure we're logged in!
  292.   If Not bLoggedIn() Then
  293.     HandleNotLoggedIn
  294.     GoTo bGetFile_GetOut
  295.   End If
  296.                     
  297.   ' Check to make sure local/remote file names have been set...
  298.   If objFTP.LocalFileName = "" Or objFTP.RemoteFileName = "" Then
  299.     DP "ERROR: Local and/or Remote File Name Not Specified"
  300.     SetError ERR_BAD_OR_MISSING_PARAM
  301.     GoTo bGetFile_GetOut
  302.   End If
  303.     
  304.   ' Set the RepresentationType and call the TYPE method...
  305.   If bBinaryFile Then
  306.     objFTP.RepresentationType = "I"
  307.   Else
  308.     objFTP.RepresentationType = "A"
  309.   End If
  310.   If Not bExecCmd(CMD_TYPE) Then GoTo bGetFile_GetOut
  311.     
  312.   ' Establish a data channel with the ftp server...This will call PASV and ConnectToDataChannel
  313.   If Not bConnectToDataChannel() Then GoTo bGetFile_GetOut
  314.   
  315.   ' We now have a data channel, so we can call RETR...
  316.   If Not bExecCmd(CMD_RETR) Then GoTo bGetFile_GetOut
  317.   
  318.   ' If we've gotten here, then the command was successful
  319.   bGetFile = True
  320.   DP "Total bytes received: " & lTotalDataBytesIn
  321.  
  322. bGetFile_GetOut:
  323.   
  324.   EndUserCall
  325.   
  326.   Exit Function
  327.   
  328. bGetFile_ErrHdlr:
  329.   HandleInternalError
  330.   EndUserCall
  331.   
  332. End Function ' bGetFile
  333.  
  334. Public Function bPutFile(Optional vntLocalFileName, Optional vntRemoteFileName, Optional vntBinaryFile, Optional vntAppend) As Boolean
  335.  
  336. ' Usage: Put a file using the low level ftp methods.  Be sure to set the CIFTP control's LocalFileName, RemoteFilename properties
  337. '             before calling this function or pass these as arguments to the funciton.  If you wish to append to a file on the server you
  338. '             must pass the optional append argument to the routine (as True).  This wraps the PASV, ConnectToDataChannel
  339. '             and STOR/APPE methods.  If vntBinaryFile is True then the RepresentationType is set to binary (I) otherwise it is set
  340. '             to ASCII.  If it is not supplied, binary is assumed
  341.  
  342.   Dim bSuccess As Boolean, bAppendToFile, bBinaryFile As Boolean
  343.  
  344.   On Error GoTo bPutFile_ErrHdlr
  345.   
  346.   bPutFile = False ' Assume worst case.
  347.   
  348.   If Not bInitUserCall("bPutFile") Then Exit Function 'GoTo bPutFile_GetOut
  349.     
  350.   ' Handle optional parameters...
  351.   If Not IsMissing(vntLocalFileName) Then objFTP.LocalFileName = CStr(vntLocalFileName)
  352.   If Not IsMissing(vntRemoteFileName) Then objFTP.RemoteFileName = CStr(vntRemoteFileName)
  353.   If Not IsMissing(vntAppend) Then bAppendToFile = CBool(vntAppend)
  354.   If Not IsMissing(vntBinaryFile) Then
  355.     bBinaryFile = CBool(vntBinaryFile)
  356.   Else
  357.     bBinaryFile = True ' Treat as a binary file if not specified.
  358.   End If
  359.                     
  360.   DumpFTPSettings (USER_FUNC_PUT_FILE)  ' Dump relevant FTP settings for putting a file
  361.  
  362.   ' Make sure we're logged in!
  363.   If Not bLoggedIn() Then
  364.     HandleNotLoggedIn
  365.     GoTo bPutFile_GetOut
  366.   End If
  367.                     
  368.   ' Check to make sure local/remote file names have been set...
  369.   If objFTP.LocalFileName = "" Or objFTP.RemoteFileName = "" Then
  370.     DP "ERROR: Local and/or Remote File Name Not Specified"
  371.     SetError ERR_BAD_OR_MISSING_PARAM
  372.     GoTo bPutFile_GetOut
  373.   End If
  374.     
  375.   ' Set the RepresentationType and call the TYPE method...
  376.   If bBinaryFile Then
  377.     objFTP.RepresentationType = "I"
  378.   Else
  379.     objFTP.RepresentationType = "A"
  380.   End If
  381.   If Not bExecCmd(CMD_TYPE) Then GoTo bPutFile_GetOut
  382.   
  383.   ' Establish a data channel with the ftp server...This will call PASV and ConnectToDataChannel
  384.   If Not bConnectToDataChannel() Then GoTo bPutFile_GetOut
  385.   
  386.   ' We now have a data channel, so we can call STOR/APPE...
  387.   If bAppendToFile Then
  388.     bSuccess = bExecCmd(CMD_APPE)
  389.   Else
  390.     bSuccess = bExecCmd(CMD_STOR)
  391.   End If
  392.   If Not bSuccess Then GoTo bPutFile_GetOut
  393.   
  394.   ' If we've gotten here, then the command was successful
  395.   bPutFile = True
  396.   DP "Total bytes sent: " & lTotalDataBytesOut
  397.  
  398. bPutFile_GetOut:
  399.   
  400.   EndUserCall
  401.   
  402.   Exit Function
  403.   
  404. bPutFile_ErrHdlr:
  405.   HandleInternalError
  406.   EndUserCall
  407.   
  408. End Function ' bPutFile
  409.  
  410. Public Function bGetDirectory(Optional vntWorkingDirectory) As Boolean
  411.   
  412. ' Usage: Use this function to populate the files collection and dir/files list boxes.  Be sure to set
  413. ' the WorkingDirectory property to the desired location before invoking this function or pass this as
  414. ' an argument to the function.  This wraps the ConnectToDataChannel, CWD, LIST and PWD methods.
  415.  
  416.   Dim bSuccess As Boolean
  417.   
  418.   On Error GoTo bGetDirectory_ErrHdlr
  419.   
  420.   bGetDirectory = False  ' Assume worst case
  421.   
  422.   If Not bInitUserCall("bGetDirectory") Then Exit Function 'GoTo bGetDirectory_GetOut
  423.                           
  424.   ' Make sure we're logged in!
  425.   If Not bLoggedIn() Then
  426.     HandleNotLoggedIn
  427.     GoTo bGetDirectory_GetOut
  428.   End If
  429.   
  430.   ' Handle optional argument...
  431.   If Not IsMissing(vntWorkingDirectory) Then objFTP.WorkingDirectory = CStr(vntWorkingDirectory)
  432.   
  433.   ' NOTE: If the LocalFileName property is set when you call the list method, the resulting output
  434.   ' is dumped to the specified file.  Therefore, be sure to reset this to a known value after doing
  435.   ' a get or a put or else you may accidentally overwrite a file you didn't intend to.
  436.   ' We set it to a file in the application directory here so that the FileClosed event will fire at the end
  437.   ' of the LIST function.
  438.   objFTP.LocalFileName = App.Path & "\$$FtpDir.tmp"
  439.   
  440.   DumpFTPSettings (USER_FUNC_GET_DIR)  ' Dump relevant FTP Get dir settings
  441.   
  442.   ' Set type to ASCII...
  443.   objFTP.RepresentationType = "A"
  444.   bSuccess = bExecCmd(CMD_TYPE)
  445.   If Not bSuccess Then GoTo bGetDirectory_GetOut
  446.                                                              
  447.   ' Get a data channell...
  448.   bSuccess = bConnectToDataChannel() ' this will call PASV and ConnectToDataChannel
  449.   If Not bSuccess Then GoTo bGetDirectory_GetOut
  450.     
  451.   ' (Possibly) change dirs to that specified in the WorkingDirectory property
  452.   bSuccess = bExecCmd(CMD_CWD)
  453.   If Not bSuccess Then GoTo bGetDirectory_GetOut
  454.    
  455.   ' Now execute the list command to update the files collection and list boxes
  456.   bSuccess = bExecCmd(CMD_LIST)
  457.   If Not bSuccess Then GoTo bGetDirectory_GetOut
  458.   
  459.   ' Update the WorkingDirectory property
  460.   bSuccess = bExecCmd(CMD_PWD)
  461.    
  462. bGetDirectory_GetOut:
  463.   
  464.   bGetDirectory = bSuccess
  465.   EndUserCall
  466.   Exit Function
  467.   
  468. bGetDirectory_ErrHdlr:
  469.    HandleInternalError
  470.    EndUserCall
  471.   
  472. End Function ' bGetDirectory
  473.  
  474. Public Function bLogout() As Boolean
  475.   
  476. ' Usage: Terminate the connection (established by bLogin) to the ftp server
  477.  
  478.   On Error GoTo bLogout_ErrHdlr
  479.   
  480.   bLogout = False ' Assume worst case
  481.   
  482.   If Not bInitUserCall("bLogout") Then Exit Function 'GoTo bLogout_GetOut
  483.   
  484.   ' Make sure we're logged in!
  485.   If Not bLoggedIn() Then
  486.     HandleNotLoggedIn
  487.     GoTo bLogout_GetOut
  488.   End If
  489.   
  490.   ' Set flag indicating user is initiating shutdown.  This is looked at in EventStateChanged when
  491.   ' the access channel closes.  If it is unexpected, corrective action can be taken.
  492.   bUserLoggingOut = True
  493.  
  494.   bLogout = bExecCmd(CMD_QUIT)
  495.   
  496. bLogout_GetOut:
  497.   
  498.   EndUserCall
  499.     
  500.   Exit Function
  501.   
  502. bLogout_ErrHdlr:
  503.   HandleInternalError
  504.   EndUserCall
  505.  
  506. End Function ' bLogout
  507.  
  508. Public Function bAbort() As Boolean
  509.  
  510. 'Usage: Abort the currently executing user command.
  511.  
  512.   If sCurrUserFuncName <> "" Then ' abort the currently executing user request
  513.     SetError ERR_CMD_ABORTED
  514.     bAbort = True
  515.     ' If the data channel is active, then terminate it via the CleanupDataConnection method
  516.     ' This allows users to abort file transfers
  517.     If bDataControlChannelOpen Then
  518.       DP "Operation terminated via bAbort, closing data channel..."
  519.       CloseDataControlChannel
  520.     End If
  521.   Else ' no user request is currently under way, so there is nothing to abort
  522.     SetError ERR_INVALID_ABORT
  523.     bAbort = False
  524.   End If
  525.   
  526. End Function ' bAbort
  527.  
  528. Public Function sGetLastError() As String
  529.  
  530. ' Usage: If an error occurs, use this function to get a human readable description of the problem
  531.  
  532.   Dim sErr As String
  533.   
  534.   On Error Resume Next
  535.   
  536.   Select Case LastError.iType
  537.     Case ERROR_TYPE_NONE           ' No Error
  538.       sErr = "No Error"
  539.     Case ERROR_TYPE_WSA             ' WSAError event fired
  540.       sErr = "WSAError - " & WSAErrDescription(CInt(LastError.lCode))
  541.     Case ERROR_TYPE_INTERNET     ' InternetError event fired
  542.       sErr = "InternetError - " & InternetErrDescription(LastError.lCode)
  543.     Case ERROR_TYPE_OTHER     ' There was an internal error in the routine (possibly caused by bad/missing user param)
  544.       sErr = sGetOtherError()
  545.     Case Else
  546.       sErr = "Unknown Error Type!"
  547.   End Select
  548.   
  549.   sGetLastError = sErr
  550.   
  551. End Function ' sGetLastError
  552.  
  553. Public Sub ServerResponse(ByVal iCode As Integer, sMsg As String)
  554.  
  555. ' Usage: This MUST be called from the ServerResponse event code for the CIFTP control that is being wrapped
  556.  
  557.   AddToSRQueue iCode, sMsg
  558.   dtLastServerResponse = Now ' record the time that server last sent info
  559.  
  560. End Sub ' ServerResponse
  561.  
  562. Public Sub EventStateChanged(iState As Integer)
  563.  
  564. ' Usage: This MUST be called from the EventStateChanged event code for the CIFTP control that is being wrapped
  565.  
  566.   On Error GoTo EventStateChanged_ErrHdlr
  567.   
  568.   Select Case iState
  569.     
  570.     Case CIFTP_FCLOSED ' File Closed
  571.       DP "File Closed"
  572.       bFileClosed = True  ' used to end LIST and RETR calls
  573.       
  574.       Case CIFTP_SCLOSED ' Socket Closed
  575.       DP "Socket Closed"
  576.     
  577.     Case CIFTP_ACCONN ' Access Control Channel Connection
  578.       DP "AccessControlChannelConnection"
  579.       bAccessControlChannelOpen = True
  580.           
  581.     Case CIFTP_ACCLOSED ' Access Channel Closed
  582.       DP "AccessControlChannelClosed"
  583.       If Not bUserLoggingOut Then
  584.         ' To-do: Logic to handle case where access control channel is unexpectedly dropped...
  585.         DP "Timeout may have occured.  Access channel lost at " & Now
  586.         DP "Last user request occured at " & dtLastUserRequest
  587.         SetError (ERR_ACCESS_CONN_LOST)
  588.       End If
  589.       bAccessControlChannelOpen = False
  590.         
  591.     Case CIFTP_DCCONN ' Data Channel Connection
  592.       DP "Data control connection established"
  593.       bDataControlChannelOpen = True
  594.       m_lTotalDataBytesIn = 0
  595.       m_lTotalDataBytesOut = 0
  596.       
  597.     Case CIFTP_DCCLOSED ' Data Channel Connection Closed
  598.       DP "Data control connection closed"
  599.       bDataControlChannelOpen = False
  600.       
  601.     Case CIFTP_DPORTSET ' Data Port Set
  602.       DP "DataPortSet"
  603. '
  604.     Case CIFTP_LBPOP ' List Boxes Populated
  605.       DP "List Boxes Populated"
  606.       
  607.      Case Else
  608.        DP "Unknown event state (" & iState & ")"
  609.     
  610.   End Select
  611.   
  612.   dtLastServerResponse = Now ' record the time that info was recieved
  613.   
  614.   Exit Sub
  615.   
  616. EventStateChanged_ErrHdlr:
  617.   HandleInternalError
  618.     
  619. End Sub ' EventStateChanged
  620.  
  621. Public Sub AccessControlPacketReceived(ByRef Packet As String)
  622.  
  623. ' Usage: This should be called from the AccessControlPacketReceived event code for the CIFTP control that is being wrapped if
  624. '             you wish to have the wrapper class track these packets.
  625.     
  626.   If bDumpAccessPackets Then
  627.     DP "+++ Access Packet Start +++"
  628.     DP Packet
  629.     DP "+++ Access Packet End +++" & vbCrLf
  630.   Else
  631.     DP "AccessControlPacketReceived"
  632.   End If
  633.   
  634.   dtLastServerResponse = Now ' record the time that the data was recieved
  635.   
  636. End Sub ' AccessControlPacketReceived
  637.  
  638. Public Sub DataControlPacketReceived(ByRef Packet As String, ByVal bytes_in As Integer)
  639.   
  640. ' Usage: This should be called from the DataControlPacketReceived event code for the CIFTP control that is being wrapped if
  641. '             you wish to have the wrapper class track these packets.  If you wish to use the timeout facilities provided by the wrapper
  642. '             you need to call this routine since it monitors the time at which packets are received.
  643.   
  644.   If bDumpDataPackets Then
  645.     DP vbCrLf
  646.     DP "--- Data Packet Start ---"
  647.     DP Packet
  648.     DP "--- Data Packet End [" & bytes_in & " bytes] ---" & vbCrLf
  649.   Else
  650.     DP "DataControlPacketReceived (" & bytes_in & ")"
  651.   End If
  652.   m_lTotalDataBytesIn = m_lTotalDataBytesIn + bytes_in
  653.   
  654.   dtLastServerResponse = Now ' record the time that data was last recieved
  655.   
  656. End Sub ' DataControlPacketReceived
  657.  
  658. Public Sub DataControlPacketSent(ByVal bytes_out As Integer)
  659.   
  660. ' Usage: This should be called from the DataControlPacketSent event code for the CIFTP control that is being wrapped if
  661. '             you wish to have the wrapper class track these packets.  If you wish to use the timeout facilities provided by the wrapper
  662. '             you need to call this routine since it monitors the time at which packets are received.
  663.   
  664.   DP "DataControlPacketSent (" & bytes_out & ")"
  665.   m_lTotalDataBytesOut = m_lTotalDataBytesOut + bytes_out   ' update byte count
  666.     
  667.   dtLastServerResponse = Now ' record the time that data was last sent
  668.     
  669. End Sub ' DataControlPacketSent
  670.  
  671. Public Sub InternetError(ByVal lErrorNumber As Long, ByRef sErrorMsg As String)
  672.   
  673. ' Usage: This MUST be called from the InternetError event code for the CIFTP control that is being wrapped
  674. '            NOTE: The high level methods are not currently wrapped by this class, so you can safely ommit this call.
  675.  
  676.   DP "InternextError (" & lErrorNumber & ") - " & InternetErrDescription(lErrorNumber)
  677.   SetError lErrorNumber, ERROR_TYPE_INTERNET
  678.   
  679. End Sub ' InternetError
  680.  
  681. Public Sub WSAError(ByVal iErrorNumber As Integer)
  682.   
  683. ' Usage: This MUST be called from the WSAError event code for the CIFTP control that is being wrapped
  684.   
  685.   ' There appears to be a bug in the CIFTP control which causes an error of -10000 to be incorrectly raised.
  686.   ' Since this does not represent a real error, we'll ignore this special case.
  687.   If iErrorNumber = -10000 Then
  688.     ' ignore this isn't a real error
  689.   Else
  690.     DP "WSAError (" & iErrorNumber & ") - " & WSAErrDescription(iErrorNumber)
  691.     SetError CLng(iErrorNumber), ERROR_TYPE_WSA
  692.   End If
  693.   
  694. End Sub 'WSAError
  695.  
  696. Public Function bLoggedIn() As Boolean
  697. 'Usage: determine if the user is logged in.  Imperfect since we just check if the access control channel is open
  698.   bLoggedIn = (bAccessControlChannelOpen = True)
  699. End Function ' bLoggedIn
  700.  
  701. Public Property Get lTotalDataBytesIn() As Long
  702. ' Usage: provides read-only access to the # of bytes RECEIVED on the data control channel
  703. ' This is usefull for tracking the status of RETR operations
  704.   lTotalDataBytesIn = m_lTotalDataBytesIn
  705. End Property
  706.  
  707. Public Property Get lTotalDataBytesOut() As Long
  708. ' Usage: provides read-only access to the # of bytes SENT on the data control channel
  709. ' This is usefull for tracking the status of STOR/APPE operations
  710.   lTotalDataBytesOut = m_lTotalDataBytesOut
  711. End Property
  712.  
  713.  
  714. '====================================================
  715. '   +++ Private Routine Implementation Starts Here +++
  716. '====================================================
  717.  
  718. Private Sub InitFTPStateMachine()
  719.   
  720. ' Load the state array with the possible server response codes for the supported commands...
  721.  
  722.   DP "Initializing state machine..."
  723.   ' USER
  724.   iFTPStateMachine(CMD_USER, SUCCESS, 1) = 230 ' logged in
  725.   iFTPStateMachine(CMD_USER, SUCCESS, 2) = 331 ' user ok, need pass
  726.     
  727.   iFTPStateMachine(CMD_USER, FAILURE, 1) = 332  ' need account to login
  728.   iFTPStateMachine(CMD_USER, FAILURE, 2) = 530  ' not logged in
  729.   iFTPStateMachine(CMD_USER, FAILURE, 3) = 500  ' syntax err, cmd not recognized
  730.   iFTPStateMachine(CMD_USER, FAILURE, 4) = 501  ' syntax err in param or arg
  731.   iFTPStateMachine(CMD_USER, FAILURE, 5) = 421  ' service not avail, closing control connection!
  732.   ' PASS
  733.   iFTPStateMachine(CMD_PASS, SUCCESS, 1) = 230 ' logged in
  734.   iFTPStateMachine(CMD_PASS, SUCCESS, 2) = 202 ' command not implemented superfulous at this site
  735.     
  736.   iFTPStateMachine(CMD_PASS, FAILURE, 1) = 332  ' need account to login
  737.   iFTPStateMachine(CMD_PASS, FAILURE, 2) = 530  ' not logged in
  738.   iFTPStateMachine(CMD_PASS, FAILURE, 3) = 500  ' syntax err, cmd not recognized
  739.   iFTPStateMachine(CMD_PASS, FAILURE, 4) = 501  ' syntax err in param or arg
  740.   iFTPStateMachine(CMD_PASS, FAILURE, 5) = 503  ' bad sequence of commands
  741.   iFTPStateMachine(CMD_PASS, FAILURE, 6) = 421  ' service not avail, closing control connection!
  742.   ' CWD
  743.   iFTPStateMachine(CMD_CWD, SUCCESS, 1) = 250  ' requested file action ok, completed
  744.     
  745.   iFTPStateMachine(CMD_CWD, FAILURE, 1) = 530  ' not logged in
  746.   iFTPStateMachine(CMD_CWD, FAILURE, 2) = 500  ' syntax err, cmd not recognized
  747.   iFTPStateMachine(CMD_CWD, FAILURE, 3) = 501  ' syntax err in param or arg
  748.   iFTPStateMachine(CMD_CWD, FAILURE, 4) = 502  ' cmd not implemented
  749.   iFTPStateMachine(CMD_CWD, FAILURE, 5) = 421  ' service not avail, closing control connection!
  750.   iFTPStateMachine(CMD_CWD, FAILURE, 6) = 550  ' action not taken (file unavail., not found, etc.)
  751.   ' CDUP
  752.   iFTPStateMachine(CMD_CDUP, SUCCESS, 1) = 200  ' command ok
  753.     
  754.   iFTPStateMachine(CMD_CDUP, FAILURE, 1) = 530  ' not logged in
  755.   iFTPStateMachine(CMD_CDUP, FAILURE, 2) = 500  ' syntax err, cmd not recognized
  756.   iFTPStateMachine(CMD_CDUP, FAILURE, 3) = 501  ' syntax err in param or arg
  757.   iFTPStateMachine(CMD_CDUP, FAILURE, 4) = 502  ' cmd not implemented
  758.   iFTPStateMachine(CMD_CDUP, FAILURE, 5) = 421  ' service not avail, closing control connection!
  759.   iFTPStateMachine(CMD_CDUP, FAILURE, 6) = 550  ' action not taken (file unavail., not found, etc.)
  760.   ' TYPE
  761.   iFTPStateMachine(CMD_TYPE, SUCCESS, 1) = 200  ' command ok
  762.     
  763.   iFTPStateMachine(CMD_TYPE, FAILURE, 1) = 530  ' not logged in
  764.   iFTPStateMachine(CMD_TYPE, FAILURE, 2) = 500  ' syntax err, cmd not recognized
  765.   iFTPStateMachine(CMD_TYPE, FAILURE, 3) = 501  ' syntax err in param or arg
  766.   iFTPStateMachine(CMD_TYPE, FAILURE, 4) = 504  ' command not implemented for specified parameter
  767.   iFTPStateMachine(CMD_TYPE, FAILURE, 5) = 421  ' service not avail, closing control connection!
  768.   ' PWD
  769.   iFTPStateMachine(CMD_PWD, SUCCESS, 1) = 257  ' "pathname" created (ok)
  770.     
  771.   iFTPStateMachine(CMD_PWD, FAILURE, 1) = 500  ' syntax err, cmd not recognized
  772.   iFTPStateMachine(CMD_PWD, FAILURE, 2) = 501  ' syntax err in param or arg
  773.   iFTPStateMachine(CMD_PWD, FAILURE, 3) = 502  ' cmd not implemented
  774.   iFTPStateMachine(CMD_PWD, FAILURE, 4) = 421  ' service not avail, closing control connection!
  775.   iFTPStateMachine(CMD_PWD, FAILURE, 5) = 550  ' action not taken (file unavail., not found, etc.)
  776.   ' SYST
  777.   iFTPStateMachine(CMD_SYST, SUCCESS, 1) = 215  ' NAME system type
  778.     
  779.   iFTPStateMachine(CMD_SYST, FAILURE, 1) = 500  ' syntax err, cmd not recognized
  780.   iFTPStateMachine(CMD_SYST, FAILURE, 2) = 501  ' syntax err in param or arg
  781.   iFTPStateMachine(CMD_SYST, FAILURE, 3) = 502  ' cmd not implemented
  782.   iFTPStateMachine(CMD_SYST, FAILURE, 4) = 421  ' service not avail, closing control connection!
  783.   ' QUIT
  784.   iFTPStateMachine(CMD_QUIT, SUCCESS, 1) = 221  ' service closing control connection
  785.     
  786.   iFTPStateMachine(CMD_QUIT, FAILURE, 1) = 500  ' syntax err, cmd not recognized
  787.   ' PASV
  788.   iFTPStateMachine(CMD_PASV, SUCCESS, 1) = 227 ' entering passive mode
  789.     
  790.   iFTPStateMachine(CMD_PASV, FAILURE, 1) = 530  ' not logged in
  791.   iFTPStateMachine(CMD_PASV, FAILURE, 2) = 500  ' syntax err, cmd not recognized
  792.   iFTPStateMachine(CMD_PASV, FAILURE, 3) = 501  ' syntax err in param or arg
  793.   iFTPStateMachine(CMD_PASV, FAILURE, 4) = 502  ' cmd not implemented
  794.   iFTPStateMachine(CMD_PASV, FAILURE, 5) = 421  ' service not avail, closing control connection!
  795.   ' LIST
  796.   iFTPStateMachine(CMD_LIST, SUCCESS, 1) = 226  ' closing data connection - requested file action ok
  797.   iFTPStateMachine(CMD_LIST, SUCCESS, 2) = 250  ' requested file action ok, completed
  798.     
  799.   iFTPStateMachine(CMD_LIST, FAILURE, 1) = 425  ' can't open data connection
  800.   iFTPStateMachine(CMD_LIST, FAILURE, 2) = 426  ' connection closed, transfer aborted
  801.   iFTPStateMachine(CMD_LIST, FAILURE, 3) = 450  ' requested file action not taken, file unavailable (.e.g. file busy)
  802.   iFTPStateMachine(CMD_LIST, FAILURE, 4) = 451  ' requested file action aborted: local error processing
  803.   iFTPStateMachine(CMD_LIST, FAILURE, 5) = 500  ' syntax err, cmd not recognized
  804.   iFTPStateMachine(CMD_LIST, FAILURE, 6) = 501  ' syntax err in param or arg
  805.   iFTPStateMachine(CMD_LIST, FAILURE, 7) = 502  ' cmd not implemented
  806.   iFTPStateMachine(CMD_LIST, FAILURE, 8) = 421  ' service not avail, closing control connection!
  807.   iFTPStateMachine(CMD_LIST, FAILURE, 9) = 530  ' not logged in
  808.   ' RETR
  809.   iFTPStateMachine(CMD_RETR, SUCCESS, 1) = 226  ' closing data connection - requested file action ok
  810.   iFTPStateMachine(CMD_RETR, SUCCESS, 2) = 250  ' requested file action ok, completed
  811.     
  812.   iFTPStateMachine(CMD_RETR, FAILURE, 1) = 425  ' can't open data connection
  813.   iFTPStateMachine(CMD_RETR, FAILURE, 2) = 426  ' connection closed, transfer aborted
  814.   iFTPStateMachine(CMD_RETR, FAILURE, 3) = 450  ' requested file action not taken, file unavailable (.e.g. file busy)
  815.   iFTPStateMachine(CMD_RETR, FAILURE, 4) = 451  ' requested file action aborted: local error processing
  816.   iFTPStateMachine(CMD_RETR, FAILURE, 5) = 500  ' syntax err, cmd not recognized
  817.   iFTPStateMachine(CMD_RETR, FAILURE, 6) = 501  ' syntax err in param or arg
  818.   iFTPStateMachine(CMD_RETR, FAILURE, 7) = 421  ' service not avail, closing control connection!
  819.   iFTPStateMachine(CMD_RETR, FAILURE, 8) = 530  ' not logged in
  820.   ' STOR
  821.   iFTPStateMachine(CMD_STOR, SUCCESS, 1) = 226  ' closing data connection - requested file action ok
  822.   iFTPStateMachine(CMD_STOR, SUCCESS, 2) = 250  ' requested file action ok, completed
  823.     
  824.   iFTPStateMachine(CMD_STOR, FAILURE, 1) = 425  ' can't open data connection
  825.   iFTPStateMachine(CMD_STOR, FAILURE, 2) = 426  ' connection closed, transfer aborted
  826.   iFTPStateMachine(CMD_STOR, FAILURE, 3) = 450  ' requested file action not taken, file unavailable (.e.g. file busy)
  827.   iFTPStateMachine(CMD_STOR, FAILURE, 4) = 451  ' requested file action aborted: local error processing
  828.   iFTPStateMachine(CMD_STOR, FAILURE, 5) = 551  ' requested action aborted, page type unknown
  829.   iFTPStateMachine(CMD_STOR, FAILURE, 6) = 552  ' requested file action aborted -- exceeded storage allocation
  830.   iFTPStateMachine(CMD_STOR, FAILURE, 7) = 532  ' need an account to stor files
  831.   iFTPStateMachine(CMD_STOR, FAILURE, 8) = 553  ' requested action not taken -- filename not allowed
  832.   iFTPStateMachine(CMD_STOR, FAILURE, 9) = 452  ' requested action not taken -- insufficient storage space in system
  833.   iFTPStateMachine(CMD_STOR, FAILURE, 10) = 500  ' syntax err, cmd not recognized
  834.   iFTPStateMachine(CMD_STOR, FAILURE, 11) = 501  ' syntax err in param or arg
  835.   iFTPStateMachine(CMD_STOR, FAILURE, 12) = 421  ' service not avail, closing control connection!
  836.   iFTPStateMachine(CMD_STOR, FAILURE, 13) = 530  ' not logged in
  837.   ' APPE
  838.   iFTPStateMachine(CMD_APPE, SUCCESS, 1) = 226  ' closing data connection - requested file action ok
  839.   iFTPStateMachine(CMD_APPE, SUCCESS, 2) = 250  ' requested file action ok, completed
  840.     
  841.   iFTPStateMachine(CMD_APPE, FAILURE, 1) = 425  ' can't open data connection
  842.   iFTPStateMachine(CMD_APPE, FAILURE, 2) = 426  ' connection closed, transfer aborted
  843.   iFTPStateMachine(CMD_APPE, FAILURE, 3) = 450  ' requested file action not taken, file unavailable (.e.g. file busy)
  844.   iFTPStateMachine(CMD_APPE, FAILURE, 4) = 451  ' requested file action aborted: local error processing
  845.   iFTPStateMachine(CMD_APPE, FAILURE, 5) = 551  ' requested action aborted, page type unknown
  846.   iFTPStateMachine(CMD_APPE, FAILURE, 6) = 552  ' requested file action aborted -- exceeded storage allocation
  847.   iFTPStateMachine(CMD_APPE, FAILURE, 7) = 532  ' need an account to stor files
  848.   iFTPStateMachine(CMD_APPE, FAILURE, 8) = 553  ' requested action not taken -- filename not allowed
  849.   iFTPStateMachine(CMD_APPE, FAILURE, 9) = 452  ' requested action not taken -- insufficient storage space in system
  850.   iFTPStateMachine(CMD_APPE, FAILURE, 10) = 500  ' syntax err, cmd not recognized
  851.   iFTPStateMachine(CMD_APPE, FAILURE, 11) = 501  ' syntax err in param or arg
  852.   iFTPStateMachine(CMD_APPE, FAILURE, 12) = 421  ' service not avail, closing control connection!
  853.   iFTPStateMachine(CMD_APPE, FAILURE, 13) = 530  ' not logged in
  854.   iFTPStateMachine(CMD_APPE, FAILURE, 14) = 550  ' action not taken (file unavail., not found, etc.)
  855.   iFTPStateMachine(CMD_APPE, FAILURE, 15) = 502  ' cmd not implemented
  856.  
  857. End Sub ' InitFTPStateMachine
  858.  
  859. Private Function bExecCmd(iCmd As Byte) As Boolean
  860.  
  861.   ' This is the work-horse function for this class module
  862.   ' It is called internally to make the actual calls to the
  863.   ' ftp commands that are supported by the CIFTP control
  864.  
  865.   ' NOTE: This function is a candidate to become public.  There are times when
  866.   ' it would be convenient for users to simply invoke one of the supported commands
  867.   ' directly (e.g. TYPE).  However, this would require additional logic to be added
  868.   ' since the routine, in its current form, assumes that a data channel has been
  869.   ' established where necessary, prior to being called.  A better strategy might
  870.   ' be to write additional public routines that perform the needed functions or
  871.   ' to write a general purpose "ProcessFTPCommand" routine that would invoke the
  872.   ' CIFTP.SendFTPCommand.
  873.   
  874.   Dim dtStart As Date, iTimeout As Integer, iSecs As Integer, bTimedOut As Boolean
  875.   
  876.   On Error GoTo bExecCmd_ErrHdlr
  877.         
  878.   bExecCmd = False   ' Assume worst case
  879.   bFileClosed = False  ' Reset flag that indicates that the FileClosed event has fired
  880.   ClearSRQueue         ' Clear the server response queue
  881.   
  882.   Select Case iCmd
  883. '    Case CMD_CONNECT ' connect to access control channel
  884.     Case CMD_USER
  885.       DP "Calling USER..."
  886.       objFTP.USER
  887.     Case CMD_PASS
  888.       DP "Calling PASS..."
  889.       objFTP.PASS
  890.     Case CMD_CWD
  891.       DP "Calling CWD..."
  892.       objFTP.CWD
  893.     Case CMD_CDUP
  894.       DP "Calling CDUP..."
  895.       objFTP.CDUP
  896.     Case CMD_QUIT
  897.       DP "Calling QUIT..."
  898.       objFTP.QUIT
  899.     Case CMD_PASV
  900.       DP "Calling PASV..."
  901.       objFTP.PASV
  902.     Case CMD_TYPE
  903.       DP "Calling TYPE..."
  904.       objFTP.TYPE
  905.     Case CMD_STOR
  906.       DP "Calling STOR..."
  907.       objFTP.STOR
  908.     Case CMD_RETR
  909.       DP "Calling RETR..."
  910.       objFTP.RETR
  911.     Case CMD_LIST
  912.       DP "Calling LIST..."
  913.       objFTP.List
  914.     Case CMD_APPE
  915.       DP "Calling APPE..."
  916.       objFTP.APPE
  917.     Case CMD_PWD
  918.       DP "Calling PWD..."
  919.       objFTP.PWD
  920.     Case CMD_SYST
  921.       DP "Calling SYST..."
  922.       objFTP.SYST
  923.     
  924.     'TO DO: add SendFTPCommand support here
  925.     
  926.     Case Else
  927.       DP "bExecCmd: ERROR - Unknown Command (" & iCmd & ")"
  928.       HandleInternalError
  929.       Exit Function
  930.   
  931.   End Select
  932.   
  933.   ' Now that we've issued the command, wait for the server's response...
  934.   bExecCmd = bWaitOnServerResponse(iCmd)
  935.     
  936.   Exit Function
  937.  
  938. bExecCmd_ErrHdlr:
  939.   HandleInternalError
  940.   ' no corrective action implemented, just fail
  941.   
  942. End Function ' bExecCmd
  943.  
  944. Private Function bWaitOnServerResponse(iCmd As Byte) As Boolean
  945.   
  946.   Dim dtStart As Date, iSecs As Integer, bTimedOut As Boolean
  947.   Dim i As Integer, j As Integer, iInitQPos As Integer
  948.   
  949.   On Error GoTo bWaitOnServerResponse_ErrHdlr
  950.   
  951.   bWaitOnServerResponse = False
  952.     
  953. ' Check the Server Response Queue for input.  If no conclusive response is found try again until
  954. ' either a definitive response is received OR an error is raised OR the timeout period expires...
  955.  
  956. CheckItOut:
  957.  
  958.   iInitQPos = iQPos
  959.   For i = 1 To iInitQPos
  960.     For j = 1 To MAX_CODES
  961.       If SRQueue(i).iCode = iFTPStateMachine(iCmd, SUCCESS, j) Then
  962.         DP "Server responded Affirmatively - " & SRQueue(i).iCode
  963.         ' We need to do some special case processing for the RETR and LIST commands
  964.         ' Both of these cause the FileClosed event to fire AFTER the server responds affirmatively,
  965.         ' so we need to wait on these events before considering the command to be completed
  966.         If iCmd = CMD_RETR Or iCmd = CMD_LIST Then
  967.           DP "Waiting on FileClosed event to complete the command..."
  968.           bWaitOnServerResponse = bWaitOnFileClosed()
  969.         Else ' otherwise, we're done
  970.           bWaitOnServerResponse = True
  971.         End If
  972.         Exit Function
  973.       ElseIf SRQueue(i).iCode = iFTPStateMachine(iCmd, FAILURE, j) Then
  974.         DP "Server Responded Negatively! - " & SRQueue(i).iCode
  975.         SetError (ERR_SERVER_REQUEST_FAILED)
  976.         CloseDataControlChannel ' make sure the data channel gets closed
  977.         Exit Function
  978.       Else
  979.         ' The server responded with a code that is not in the state table
  980.         ' Some servers do not strictly follow the RFC, so they may return
  981.         ' a failure code that is not in our state table.  if the code is >= 400,
  982.         ' then the command has failed.  Handle such cases by treating it as
  983.         ' a failed request...
  984.         If SRQueue(i).iCode >= 400 Then
  985.           DP "Server responded with non-standard negative response code - " & SRQueue(i).iCode
  986.           DP "Requested command did not succeed."
  987.           SetError (ERR_SERVER_REQUEST_FAILED)
  988.           CloseDataControlChannel  ' make sure the data channel gets closed
  989.           Exit Function
  990.         End If
  991.       End If
  992.     Next j
  993.   Next i
  994.   
  995.   ' Inconclusive response so pound sand and check again...
  996.   If iQPos <> 0 Then DP "Server did not respond conclusively, waiting for more server responses..."
  997.   bTimedOut = False
  998.   dtLastServerResponse = Now
  999.   While iQPos = iInitQPos And Not bTimedOut And Not bFTPError()
  1000.     DoEvents
  1001.     If iTimeoutValue > 0 Then ' if a timeout value exists, then check to see if the specified threshold has been reached
  1002.       iSecs = DateDiff("s", Now, dtLastServerResponse)
  1003.       If Abs(iSecs) > iTimeoutValue Then bTimedOut = True
  1004.     End If
  1005.   Wend
  1006.   If iQPos <> iInitQPos Then
  1007.     DP "Checking Response queue for new input..."
  1008.     GoTo CheckItOut ' Try again
  1009.   ElseIf bTimedOut = True Then
  1010.     DP "Timed out waiting for server response!"
  1011.     SetError (ERR_TIMED_OUT)
  1012.   Else
  1013.     ' bFTPError is True
  1014.   End If
  1015.   
  1016.   Exit Function
  1017.  
  1018. bWaitOnServerResponse_ErrHdlr:
  1019.   HandleInternalError
  1020.   
  1021. End Function ' WaitOnServerResponse
  1022.  
  1023. Private Sub CloseDataControlChannel()
  1024.   
  1025. ' Utility routine that closes the data control channel by calling CleanupDataConnection
  1026.  
  1027.   On Error GoTo bCloseControlChannel_ErrHdlr
  1028.   
  1029.   If bDataControlChannelOpen Then
  1030.     DP "Closing data channel via CleanupDataConnection..."
  1031.     objFTP.CleanupDataConnection
  1032.     bDataControlChannelOpen = False ' this should be set in EventStateChanged, but let's make sure
  1033.   End If
  1034.  
  1035.   Exit Sub
  1036.   
  1037. bCloseControlChannel_ErrHdlr:
  1038.   HandleInternalError
  1039.   
  1040. End Sub ' CloseDataControlChannel
  1041.  
  1042. Private Function bWaitOnFileClosed() As Boolean
  1043.  
  1044. ' Wait for the file closed event to fire or for the timeout period to expire.
  1045. ' Returns True if FileClosed fires, False if timeout expires or an error occurs
  1046.   
  1047.   Dim dtStart As Date, bTimedOut As Boolean, iSecs As Integer
  1048.   
  1049.   dtStart = Now
  1050.   While Not bFileClosed And Not bTimedOut And Not bFTPError()
  1051.     DoEvents
  1052.     If iTimeoutValue > 0 Then ' if a timeout value exists, then check to see if the specified threshold has been reached
  1053.       iSecs = DateDiff("s", Now, dtStart)
  1054.       If Abs(iSecs) > iTimeoutValue Then bTimedOut = True
  1055.     End If
  1056.   Wend
  1057.   bWaitOnFileClosed = bFileClosed
  1058.   
  1059.   If bTimedOut Then
  1060.     ' sometimes the FileClosed event will not fire even though the requested action has completed.
  1061.     ' This is an anomaly in the CIFTP control
  1062.     DP "Timed out waiting for FileClosed event.  Request MAY NOT have completed"
  1063.     SetError (ERR_TIMED_OUT)
  1064.   End If
  1065.   
  1066. End Function ' bWaitOnFileClosed
  1067.  
  1068. Private Sub SetError(lCode As Long, Optional vntErrorType)
  1069.   
  1070.   ' By default errors are assumed to be type "OTHER" (non WSA/Internet)
  1071.   If IsMissing(vntErrorType) Then
  1072.     LastError.iType = ERROR_TYPE_OTHER
  1073.   Else
  1074.     LastError.iType = CInt(vntErrorType)
  1075.   End If
  1076.   LastError.lCode = lCode
  1077.   
  1078. End Sub ' SetError
  1079.  
  1080.  
  1081. Private Function sGetOtherError() As String
  1082.    
  1083.    Dim sErr As String
  1084.    
  1085.    On Error Resume Next
  1086.    
  1087.    Select Case LastError.lCode
  1088.       Case ERR_ROUTINE_ERROR
  1089.         sErr = Error(iInternaleError)
  1090.       Case ERR_BAD_OR_MISSING_PARAM
  1091.         sErr = "Bad or Missing Parameter"
  1092.       Case ERR_USER_ERROR
  1093.         sErr = "User Error"
  1094.       Case ERR_CLASS_NOT_INITIALIZED ' user is calling a function w/o first initializing the class
  1095.         sErr = "Class not initialized.  bInit must be called before using any class module functions."
  1096.       Case ERR_UNEXPECTED_STATE   ' e.g. user calls bLogout, but there access channel is closed
  1097.         sErr = "Unexpected FTP state encountered"
  1098.       Case ERR_SERVER_REQUEST_FAILED
  1099.         sErr = sGetContentOfSRQueue
  1100.       Case ERR_ACCESS_CONN_LOST
  1101.         sErr = "Access control channel dropped unexpectedly" & vbCrLf & "You may have issued a command without successfully logging in"
  1102.       Case ERR_TIMED_OUT
  1103.         sErr = "Timeout expired waiting for operation to complete"
  1104.       Case ERR_INVALID_FUNC_CALL
  1105.         sErr = "Attempted to invoke a function while another function was still executing"
  1106.       Case ERR_CMD_ABORTED
  1107.         sErr = "Function was aborted (via bAbort) before completing"
  1108.       Case ERR_INVALID_ABORT
  1109.         sErr = "User called bAbort, but no function was executing"
  1110.       Case ERR_NOT_CONNECTED
  1111.         sErr = "Not connected to ftp server"
  1112.       Case Else
  1113.         sErr = "Unknown error (" & LastError.lCode & ")"
  1114.    End Select
  1115.    sGetOtherError = sErr
  1116.  
  1117. End Function ' sGetInternalError
  1118.    
  1119. Private Sub AddToSRQueue(ByVal iCode As Integer, sMsg As String)
  1120.   
  1121.   On Error GoTo AddToSRQueue_ErrHdlr
  1122.   
  1123.   iQPos = iQPos + 1
  1124.   If iQPos > QUEUE_SIZE Then iQPos = 1
  1125.   
  1126.   SRQueue(iQPos).iCode = iCode
  1127.   SRQueue(iQPos).sMsg = sMsg
  1128.   
  1129. '  DP "SRQueue(" & iQPos & ") = " & iCode
  1130.   
  1131.   Exit Sub
  1132.  
  1133. AddToSRQueue_ErrHdlr:
  1134.   HandleInternalError
  1135.   
  1136. End Sub ' AddToSRQueue
  1137.  
  1138. Private Function bCheckSRQueue(ByVal iCode As Integer) As Boolean
  1139.   
  1140.   On Error GoTo bCheckSRQueue_ErrHdlr
  1141.   
  1142.   Dim i As Integer
  1143.   
  1144.   bCheckSRQueue = False
  1145.   For i = 1 To iQPos
  1146.     If SRQueue(i).iCode = iCode Then
  1147.       bCheckSRQueue = True
  1148.       Exit Function
  1149.     End If
  1150.   Next
  1151.   
  1152.   Exit Function
  1153.   
  1154. bCheckSRQueue_ErrHdlr:
  1155.   HandleInternalError
  1156.   
  1157. End Function ' bCheckSRQueue
  1158.  
  1159. Private Function sGetContentOfSRQueue() As String
  1160.   
  1161.   Dim i As Integer
  1162.   
  1163.   On Error Resume Next
  1164.   
  1165.   For i = 1 To iQPos
  1166. '    sGetContentOfSRQueue = sGetContentOfSRQueue & SRQueue(i).iCode & " - " & SRQueue(i).sMsg & vbCrLf
  1167.     sGetContentOfSRQueue = sGetContentOfSRQueue & SRQueue(i).sMsg & vbCrLf
  1168.   Next
  1169.  
  1170. End Function ' sGetContentOfSRQueue
  1171.  
  1172. Private Sub ClearSRQueue()
  1173.   
  1174.   On Error Resume Next
  1175.   
  1176.   Dim i As Integer
  1177.   For i = 1 To iQPos
  1178.     SRQueue(i).iCode = 0
  1179.     SRQueue(i).sMsg = ""
  1180.   Next
  1181.   iQPos = 0
  1182.  
  1183. End Sub ' ClearSRQueue
  1184.  
  1185.  
  1186. Private Sub HandleNotLoggedIn()
  1187.     
  1188.   ' This should be called if the user attempts to make a function call without first logging in to the server
  1189.   ' Call bLoggedIn to determine if a server connection is active.
  1190.  
  1191.   DP "ERROR: Not connected to ftp server"
  1192.   SetError (ERR_NOT_CONNECTED)
  1193.  
  1194. End Sub ' HandleNotLoggedIn
  1195.  
  1196. Private Sub DP(sMsg As String)
  1197.   
  1198.   Debug.Print sMsg
  1199.   
  1200.   ' Could add code here to dump output to a form, listbox, file, etc.
  1201.   ' This would be usefull for run time debugging/auditing
  1202.   
  1203. End Sub ' DP
  1204.  
  1205. Private Sub Class_Initialize()
  1206.     
  1207.   ' Set the initial values of class members...
  1208.   Set objFTP = Nothing
  1209.   iTimeoutValue = DEFAULT_TIMEOUT
  1210.   
  1211.   ' You may want to turn these on for debugging purposes.
  1212.   ' This can be done here or dynamically from client side code
  1213. '  bDumpAccessPackets = True
  1214. '  bDumpDataPackets = True
  1215.   
  1216. End Sub
  1217.  
  1218. Private Function bIsIPAddress(sHostName As String) As Boolean
  1219.  
  1220. ' Determine if the string passed in represents a valid IP address
  1221. ' While crude and hardly bullet proof, this will work with valid IPs
  1222.  
  1223.    Dim ip(4) As String, s As String, iLen As Integer, i As Integer
  1224.    On Error GoTo bIPAddress_ErrHdlr
  1225.     
  1226.  bIsIPAddress = False ' Assume it's not an IP address
  1227.  s = sHostName
  1228.  i = InStr(1, s, ".")
  1229.  If i <> 0 Then
  1230.    iLen = Len(s)
  1231.    ip(1) = Left(s, i - 1)
  1232.    s = Right(s, iLen - i)
  1233.    i = InStr(1, s, ".")
  1234.    If i <> 0 Then
  1235.      iLen = Len(s)
  1236.      ip(2) = Left(s, i - 1)
  1237.      s = Right(s, iLen - i)
  1238.      i = InStr(1, s, ".")
  1239.      If i <> 0 Then
  1240.        iLen = Len(s)
  1241.        ip(3) = Left(s, i - 1)
  1242.        s = Right(s, iLen - i)
  1243.        i = InStr(1, s, ".")
  1244.        If i = 0 Then
  1245.          ip(4) = s
  1246.          ' if we've gotten this far and all tokens are numeric and less than 3 digits, then we can treat it as an IP address
  1247.          For i = 1 To 4
  1248.            If Not IsNumeric(ip(i)) Or Len(ip(i)) > 3 Then
  1249.              Exit Function
  1250.            Else ' also make sure the numbers are within the valid IP limits
  1251.              If CInt(ip(i)) < 0 Or CInt(ip(i)) > 255 Then
  1252.                Exit Function
  1253.              End If
  1254.            End If
  1255.          Next i
  1256.          bIsIPAddress = True ' passed all criteria, so we'll treat it as an IP address
  1257.        End If
  1258.      End If
  1259.    End If
  1260.  End If
  1261.  
  1262.  Exit Function
  1263.   
  1264. bIPAddress_ErrHdlr:
  1265.   HandleInternalError
  1266.   ' if any errors occur processing the name, we don't treat it as an IP address
  1267.   
  1268. End Function ' bNameIsIPAddress
  1269.  
  1270. Private Sub HandleInternalError()
  1271.   
  1272.   DP "ERROR (" & Err.Number & ") - " & Err.Description
  1273.   iInternaleError = Err.Number ' save error number for sGetLastError routine
  1274.   SetError ERR_ROUTINE_ERROR
  1275.   
  1276. End Sub ' HandleInternalError
  1277.  
  1278. Private Function bConnectToAccessControlChannel() As Boolean
  1279.   
  1280.   Dim dtStart As Date, iSecs As Integer, bTimedOut As Boolean
  1281.       
  1282.   On Error GoTo bConnectToAccessControlChannel_ErrHdlr
  1283.     
  1284.   bConnectToAccessControlChannel = False ' Assume worst case
  1285.   
  1286.   If bAccessControlChannelOpen = True Then
  1287.     bConnectToAccessControlChannel = True ' we're already connected, so we're done
  1288.     Exit Function
  1289.   End If
  1290.   
  1291.   ' We don't have an active access control channel, so request one...
  1292.   
  1293.   ' Wait until the AccessControlChannelConnection event fires (it sets bAccessControlChannelOpen)...
  1294.   bTimedOut = False
  1295.   dtStart = Now
  1296.   DP "Calling ConnectToAccessControlChannel..."
  1297.   objFTP.ConnectToAccessControlChannel
  1298.   While bAccessControlChannelOpen = False And Not bTimedOut And Not bFTPError()
  1299.     DoEvents
  1300.     If iTimeoutValue > 0 Then ' if a timeout value exists, then check to see if the specified threshold has been reached
  1301.       iSecs = DateDiff("s", Now, dtStart)
  1302.       If Abs(iSecs) > iTimeoutValue Then bTimedOut = True
  1303.     End If
  1304.   Wend
  1305.   ' Set the result value -- if we didn't time out then we got the requested connection...
  1306.   
  1307.   bConnectToAccessControlChannel = (bTimedOut = False And Not bFTPError())
  1308.   Exit Function
  1309.   
  1310. bConnectToAccessControlChannel_ErrHdlr:
  1311.   HandleInternalError
  1312.  
  1313. End Function ' bConnectToAccessControlChannel
  1314.  
  1315. Private Function bConnectToDataChannel() As Boolean
  1316.   
  1317. '  Has error handling which expects it to only be called from within a public user function (bGetFile, bPutFile, etc.)
  1318.  
  1319.   Dim dtStart As Date, iSecs As Integer, bTimedOut As Boolean, bSuccess As Boolean
  1320.       
  1321.   On Error GoTo bConnectToDataChannel_ErrHdlr
  1322.   
  1323.   bConnectToDataChannel = False ' Assume worst case
  1324.   ClearErrorFlags ' clear flags indicating that InternetError or WSAError fired
  1325.       
  1326.   bSuccess = bExecCmd(CMD_PASV)
  1327.   If Not bSuccess Then Exit Function ' <-- Early out!
  1328.     
  1329.   Debug.Print "Data Port Established (" & objFTP.DataPort & ")"
  1330.   
  1331.   ' Now request a Data Channel...
  1332.   bDataControlChannelOpen = False ' reset flag before requesting a channel
  1333.                                                      ' We should probably check to see if a data channel is already active
  1334.   Debug.Print "Calling ConnectToDataChannel..."
  1335.   dtStart = Now
  1336.   objFTP.ConnectToDataChannel
  1337.   
  1338.   ' ===================================== NOTE =====================================
  1339.   ' If we are performing an opperation that sends data to the server (APPE/STOR),
  1340.   ' then we cannot call DoEvents between the time we connect to the data channel and
  1341.   ' the time that we issue the APPE or STOR method.  This is because the control
  1342.   ' uses the windows message queue and DoEvents causes potential synchronization
  1343.   ' problems.  Therefore, we do an early exit in this case and allow bPutFile to
  1344.   ' immediately call STOR or APPE.
  1345.   ' ===================================== NOTE =====================================
  1346.   
  1347.   If sCurrUserFuncName = "bPutFile" Then ' see above note
  1348.     bConnectToDataChannel = True
  1349.     Exit Function
  1350.   End If
  1351.   
  1352.   ' Wait until the DataChannelConnection event fires and sets our flag...
  1353.   While bDataControlChannelOpen = False And Not bTimedOut And Not bFTPError()
  1354.     DoEvents
  1355.      If iTimeoutValue > 0 Then ' if a timeout value exists, then check to see if the specified threshold has been reached
  1356.       iSecs = DateDiff("s", Now, dtStart)
  1357.       If Abs(iSecs) > iTimeoutValue Then bTimedOut = True
  1358.     End If
  1359.   Wend
  1360.   ' Set the result value -- if we didn't time out then we got the requested connection...
  1361.   bConnectToDataChannel = (bTimedOut = False And Not bFTPError())
  1362.  
  1363.   Exit Function
  1364.   
  1365. bConnectToDataChannel_ErrHdlr:
  1366.   HandleInternalError
  1367.   
  1368. End Function ' bConnectToDataChannel
  1369.  
  1370. Private Sub Class_Terminate()
  1371.   If bInitialized Then
  1372.     Set objFTP = Nothing
  1373.   End If
  1374. End Sub
  1375.  
  1376. Private Sub DumpFTPSettings(iFunction As Integer)
  1377.   
  1378.   On Error Resume Next
  1379.   
  1380.   ' Dumps the relevant FTP settings for the specified function
  1381.   With objFTP
  1382.     Select Case iFunction
  1383.       Case USER_FUNC_LOGIN
  1384.         DP "HostName = " & .HostName
  1385.         DP "HostAddress = " & .HostAddress
  1386.         DP "AccessPort = " & .AccessPort
  1387.         DP "LoginName = " & .LoginName
  1388.         DP "Password = " & .Password
  1389.         DP "WorkingDirectory = " & .WorkingDirectory
  1390.       Case USER_FUNC_LOGOUT
  1391.         DP "HostName = " & .HostName
  1392.         DP "HostAddress = " & .HostAddress
  1393.       Case USER_FUNC_GET_DIR
  1394.         DP "HostName = " & .HostName
  1395.         DP "HostAddress = " & .HostAddress
  1396.         DP "AccessPort = " & .AccessPort
  1397.         DP "WorkingDirectory = " & .WorkingDirectory
  1398.       Case USER_FUNC_GET_FILE, USER_FUNC_PUT_FILE
  1399.         DP "HostName = " & .HostName
  1400.         DP "HostAddress = " & .HostAddress
  1401.         DP "AccessPort = " & .AccessPort
  1402.         DP "WorkingDirectory = " & .WorkingDirectory
  1403.         DP "LocalFileName = " & .LocalFileName
  1404.         DP "RemoteFileName = " & .RemoteFileName
  1405.       Case Else
  1406.         DP "Unknown function value (" & iFunction & ")"
  1407.     End Select
  1408.   End With
  1409.   DP vbCrLf
  1410.   
  1411. End Sub ' DumpFTPSettings
  1412.   
  1413. Private Function bInitUserCall(sFuncName As String) As Boolean
  1414.   
  1415. ' NOTE: This should be called at the beginning of each public functions!
  1416.   
  1417. #If Win32 Then
  1418.   Const Pointer = 13 '  ccArrowHourglass
  1419. #Else
  1420.   Const Pointer = 11 'ccArrow
  1421. #End If
  1422.  
  1423.   On Error GoTo bInitUserCall_ErrHdlr
  1424.   
  1425.   ' Don't allow users to call a function if there is another function currently executing...
  1426.   If sCurrUserFuncName <> "" Then
  1427.     DP "Error user attempted to invoke '" & sFuncName & "' while another function ('" & sCurrUserFuncName & "') was in progress"
  1428. '    SetError ERR_INVALID_FUNC_CALL
  1429. ' We don't want to raise an error here because it will cause the currently executing function to potentially fail
  1430.     Exit Function
  1431.   Else
  1432.     sCurrUserFuncName = sFuncName
  1433.   End If
  1434.   
  1435.   bInitUserCall = False ' Assume worst case
  1436.   
  1437.   ' Dump header info...
  1438.   DP vbCrLf & "============ " & sFuncName & " Started at " & Now & " ============" & vbCrLf
  1439.   ' Reset error flags...
  1440.   ClearErrorFlags
  1441.   ' Set time at which last function was called by the user...
  1442.   dtLastUserRequest = Now
  1443.   ' Determine if the class module has been initialized...
  1444.   bInitUserCall = bClassInitialized()
  1445.   ' If we failed then clean up...
  1446.   If Not bInitUserCall Then ' failed...
  1447.     DP sGetLastError
  1448.     EndUserCall
  1449.   Else ' success...
  1450.     objFTP.Parent.MousePointer = Pointer ' change pointer to hourglass.  EndUserCall resets it
  1451.   End If
  1452.   
  1453.   Exit Function
  1454.   
  1455. bInitUserCall_ErrHdlr:
  1456.   HandleInternalError
  1457.   
  1458. End Function ' bInitUserCall
  1459.  
  1460. Private Sub EndUserCall()
  1461.   
  1462.   On Error Resume Next
  1463.   
  1464.   ' NOTE: This must be called at the end of ALL public functions!
  1465.   
  1466.   ' Dump foter info...
  1467.   DP vbCrLf & "=========== " & sCurrUserFuncName & " Completed at " & Now & " ===========" & vbCrLf
  1468.   sCurrUserFuncName = "" ' clear function name variable (this will allow users to call another function)
  1469.  
  1470.   If bInitialized Then
  1471.    objFTP.Parent.MousePointer = 0 ' ccDefault    ' change pointer to default
  1472.    ' [should probably save the original shape and restore that,since it may not be the default]
  1473.   End If
  1474.   
  1475. End Sub ' EndUserCall
  1476.  
  1477.  
  1478. Private Function bClassInitialized() As Boolean
  1479.   
  1480.   ' Tests to see if the class has been initialized (via bInit function)
  1481.   
  1482.   If Not bInitialized Then SetError ERR_CLASS_NOT_INITIALIZED
  1483.   bClassInitialized = bInitialized
  1484.   
  1485. End Function ' bClassInitialized
  1486.  
  1487. 'Property Let WorkingDirectory(s As String)
  1488. '  objFTP.WorkingDirectory = s
  1489. 'End Property
  1490.  
  1491. 'Property Get WorkingDirectory() As String
  1492. '  WorkingDirectory = objFTP.WorkingDirectory
  1493. 'End Property
  1494.  
  1495. Private Sub ClearErrorFlags()
  1496.   
  1497.   ' Internal routine that resets the error flags.  This is called in each of the supporting
  1498.   ' functions before initiating FTP requests.
  1499.   
  1500.   LastError.iType = ERROR_TYPE_NONE
  1501.   LastError.lCode = 0
  1502.  
  1503. End Sub ' ClearErrorFlags
  1504.  
  1505. Private Function bFTPError() As Boolean
  1506.   
  1507.   ' Internal support function to determine if the control fired an error event.
  1508.   ' Note that this function relies on the flags being set in the WSAError
  1509.   ' and InternetError events...Or by an On Error Handler
  1510.     
  1511.   bFTPError = (ERROR_TYPE_NONE <> LastError.iType)
  1512.  
  1513. End Function ' bFTPError
  1514.  
  1515.  
  1516.  
  1517.